using System;
using System.Diagnostics.Contracts;
using EPiServer.Core;
using EPiServer.SpecializedProperties;

namespace OpenWaves.EPiServer.CustomProperties
{
    [ContractClass(typeof(XhtmlStringPropertyContract<>))]
    public abstract class XhtmlStringProperty<T> : PropertyXhtmlString, IPropertyData<T>
    {
        private T value;
        private bool valueLoaded;

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",
           Justification = "default value in base constructor is set befere value is assigned in derived class.")]
        protected XhtmlStringProperty()
        {
            Contract.Requires(typeof(T) != typeof(string), "Implementation details of this class assume T is not string (see LoadData method).");

            this.SetDefaultValue();
        }

        public override Type PropertyValueType
        {
            get { return Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); }
        }

        public T GetValue()
        {
            if (this.valueLoaded)
                return this.value;
            
            // when value is null even though LoadData was not called we return default value
            if (IsNull )
            {
                return value;
            }

            this.LoadDataFromLongString(this.LongString);
            return value;
        }

        public void SetValue(T value)
        {
            this.SetPropertyValue(value, delegate
            {
                if (this.IsDefaultValue(value))
                {
                    this.Clear();
                }
                else
                {
                    this.value = value;
                    base.Value = this.SerializeValueWithCheck(value);
                }
            });

            this.valueLoaded = true;
        }

        public override object Value
        {
            get { return this.GetValue(); }
            set { this.SetValue((T)value); }
        }

        protected override void SetDefaultValue()
        {
            base.SetDefaultValue();
            this.value = this.DefaultValue;
        }

        protected virtual bool IsDefaultValue(T value)
        {
            return Equals(value, this.DefaultValue);
        }

        public override void LoadData(object value)
        {
            // HACK: EPi sometimes (from CreateLanguageBranch) will pass deserialized value
            if (value is T)
            {
                base.LoadData(this.SerializeValueWithCheck((T)value));
                this.SetValue((T)value);
            }
            else
            {
                this.LoadDataFromLongString((string)value);
                base.LoadData(value);
            }
        }

        public override void RemapPermanentLinkReferences(System.Collections.Generic.IDictionary<Guid, Guid> idMap)
        {
            base.RemapPermanentLinkReferences(idMap);
            this.LoadDataFromLongString(this.LongString);
        }

        private void LoadDataFromLongString(string longString)
        {
            this.value = this.DeserializeValueWithCheck(longString);
            this.valueLoaded = true;
        }

        private T DeserializeValueWithCheck(string value)
        {
            try
            {
                return this.DeserializeValue(value);
            }
            catch (Exception e)
            {
                throw new PropertySerializationException(
                    "Failed to deserialize property value.", this.Name, this.GetParentLink(), value, e);
            }
        }

        private string SerializeValueWithCheck(T value)
        {
            try
            {
                return this.SerializeValue(value);
            }
            catch (Exception e)
            {
                throw new PropertySerializationException(
                    "Failed to serialize property value.", this.Name, this.GetParentLink(), value, e);
            }
        }

        public override PropertyData ParseToObject(string value)
        {
            return this.CreateInstance(this.DeserializeValueWithCheck(value));
        }

        public override void ParseToSelf(string value)
        {
            this.SetValue(this.DeserializeValueWithCheck(value));
        }

        protected abstract T DefaultValue { get; }
        protected abstract XhtmlStringProperty<T> CreateInstance(T value);
        protected abstract string SerializeValue(T value);
        protected abstract T DeserializeValue(string serializedValue);
    }

    [ContractClassFor(typeof(XhtmlStringProperty<>))]
    abstract class XhtmlStringPropertyContract<T> : XhtmlStringProperty<T>
    {
        protected override XhtmlStringProperty<T> CreateInstance(T value)
        {
            Contract.Ensures(Contract.Result<XhtmlStringProperty<T>>() != null);
            throw new NotImplementedException();
        }
    }
}